/*
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, 
 * MA  02110-1301, USA.
 *
 * http://www.gnu.org/copyleft/gpl.html
 */
package com.aionemu.gameserver.ai;

import org.apache.log4j.Logger;

import com.aionemu.gameserver.model.Position;
import com.aionemu.gameserver.model.gameobjects.Creature;
import com.aionemu.gameserver.model.gameobjects.NpcWhichNotGatherable;
import com.aionemu.gameserver.model.gameobjects.VisibleObject;
import com.aionemu.gameserver.skillengine.model.SkillTemplate;
import com.aionemu.gameserver.taskmanager.AbstractIterativePeriodicTaskManager;
import com.aionemu.gameserver.utils.MathUtil;

/**
 * @author Mr. Poke
 *
 */
public abstract class AbstractAI extends NoActionAI
{

	private static Logger				log					= Logger.getLogger(AbstractAI.class);
	
	private static final int FOLLOW_INTERVAL = 1000;
	private static final int ATTACK_FOLLOW_INTERVAL = 500;
	
	private static final class FollowTaskManager extends AbstractIterativePeriodicTaskManager<AbstractAI>
	{
		private static final FollowTaskManager _instance = new FollowTaskManager();
		
		private static FollowTaskManager getInstance()
		{
			return _instance;
		}
		
		private FollowTaskManager()
		{
			super(FOLLOW_INTERVAL);
		}
		
		@Override
		protected void callTask(AbstractAI task)
		{
			task.followTarget();
		}
		
		@Override
		protected String getCalledMethodName()
		{
			return "followTarget()";
		}
	}
	
	private static final class AttackFollowTaskManager extends AbstractIterativePeriodicTaskManager<AbstractAI>
	{
		private static final AttackFollowTaskManager _instance = new AttackFollowTaskManager();
		
		private static AttackFollowTaskManager getInstance()
		{
			return _instance;
		}
		
		private AttackFollowTaskManager()
		{
			super(ATTACK_FOLLOW_INTERVAL);
		}
		
		@Override
		protected void callTask(AbstractAI task)
		{
			task.followTarget();
		}
		
		@Override
		protected String getCalledMethodName()
		{
			return "attackFollowTarget()";
		}
	}

	public final synchronized void startFollow(Creature target)
	{
		if (target == null)
		{
			stopFollow();
			return;
		}
		
		followTarget = target;
		
		FollowTaskManager.getInstance().startTask(this);
		followTarget();
	}
	
	public final synchronized void startFollow(Creature target, float range)
	{
		if (target == null)
		{
			stopFollow();
			return;
		}
		
		followTarget = target;
		followRange = range;
		
		AttackFollowTaskManager.getInstance().startTask(this);
		followTarget();
	}

	public final synchronized void stopFollow()
	{
		FollowTaskManager.getInstance().stopTask(this);
		AttackFollowTaskManager.getInstance().stopTask(this);
		
		followTarget = null;
	}

	/** Current long-term intention */
	private CtrlIntention intention = CtrlIntention.IDLE;
	
	/** Current long-term intention parameter */
	protected Object intentionArg0;
	/** Current long-term intention parameter */
	private Object intentionArg1;

	protected NpcWhichNotGatherable owner;
	
	protected NpcWhichNotGatherable target;
	
	protected NpcWhichNotGatherable followTarget;
	
	private IntentionCommand nextIntention = null;

	protected float followRange;
	private static final class IntentionCommand
	{
		private final CtrlIntention crtlIntention;
		private final Object arg0;
		private final Object arg1;
		
		private IntentionCommand(CtrlIntention pIntention, Object pArg0, Object pArg1)
		{
			crtlIntention = pIntention;
			arg0 = pArg0;
			arg1 = pArg1;
		}
	}

	/**
	 * @param owner
	 */
	public AbstractAI(Creature owner)
	{
		this.owner = owner;
	}

	/**
	 * 
	 */
	public synchronized final void followTarget()
	{
		double distance = MathUtil.getDistance(owner, followTarget);
		if (distance > 50 || owner.getKnownList().knowns(followTarget))
			setIntention(CtrlIntention.IDLE);
		
		moveToPawn(followTarget, followRange);
	}

	/**
	 * Return the L2Character managed by this Accessor AI.<BR>
	 * <BR>
	 */
	@Override
	public VisibleObject getOwner()
	{
		return owner;
	}
	
	public synchronized void setAttackTarget(Creature target)
	{
		if (intention == CtrlIntention.ATTACK)
		{
			intentionArg0 = target;
		}
		else
			log.warn("", new IllegalStateException());
	}

	/**
	 * Return current attack target.<BR>
	 * <BR>
	 */
	@Override
	public Creature getAttackTarget()
	{
		return intention == CtrlIntention.ATTACK ? (Creature)intentionArg0 :  null;
	}

	/**
	 * Return the current cast target.<BR>
	 * <BR>
	 */
	public final Creature getCastTarget()
	{
		return intention == CtrlIntention.CAST ? (Creature)intentionArg1 :  null;
	}

	/**
	 * Set the Intention of this AbstractAI.<BR>
	 * 
	 * @param intention The new Intention to set to the AI
	 * @param arg0 The first parameter of the Intention
	 * @param arg1 The second parameter of the Intention
	 */
	protected synchronized void changeIntention(CtrlIntention intention, Object arg0, Object arg1)
	{
		this.intention = intention;
		this.intentionArg0 = arg0;
		this.intentionArg1 = arg1;
	}
	
	public final Object getIntentionArg0()
	{
		return intentionArg0;
	}
	
	public final Object getIntentionArg1()
	{
		return intentionArg1;
	}
	
	/**
	 * Launch the CreatureAI onIntention method corresponding to the new Intention.<BR>
	 * <BR>
	 * <FONT COLOR=#FF0000><B> <U>Caution</U> : Stop the FOLLOW mode if necessary</B></FONT><BR>
	 * <BR>
	 * 
	 * @param intention The new Intention to set to the AI
	 */
	@Override
	public final void setIntention(CtrlIntention intention)
	{
		setIntention(intention, null, null);
	}
	
	/**
	 * Launch the CreatureAI onIntention method corresponding to the new Intention.<BR>
	 * 
	 * @param intention The new Intention to set to the AI
	 * @param arg0 The first parameter of the Intention (optional target)
	 */
	@Override
	public final void setIntention(CtrlIntention intention, Object arg0)
	{
		setIntention(intention, arg0, null);
	}

	/**
	 * Launch the L2CharacterAI onIntention method corresponding to the new Intention.<BR>
	 * <BR>
	 * <FONT COLOR=#FF0000><B> <U>Caution</U> : Stop the FOLLOW mode if necessary</B></FONT><BR>
	 * <BR>
	 * 
	 * @param intention The new Intention to set to the AI
	 * @param arg0 The first parameter of the Intention (optional target)
	 * @param arg1 The second parameter of the Intention (optional target)
	 */
	@Override
	public final void setIntention(CtrlIntention intention, Object arg0, Object arg1)
	{
		// Cancel saved intention as the current one overrides it
		clearNextIntention();

		// Stop the follow mode if necessary
		if (intention != CtrlIntention.FOLLOW && intention != CtrlIntention.ATTACK)
			stopFollow();
		
		// Launch the onIntention method of the L2CharacterAI corresponding to the new Intention
		switch (intention)
		{
			case IDLE:
				onIntentionIdle();
				break;
			case ACTIVE:
				onIntentionActive();
				break;
			case REST:
				onIntentionRest();
				break;
			case ATTACK:
				onIntentionAttack((Creature)arg0);
				break;
			case CAST:
				onIntentionCast((SkillTemplate)arg0);
				break;
			case MOVE_TO:
				onIntentionMoveTo((Position)arg0);
				break;
			case FOLLOW:
				onIntentionFollow((Creature)arg0);
				break;
		}
	}

	protected abstract void onIntentionIdle();
	
	protected abstract void onIntentionActive();
	
	protected abstract void onIntentionRest();
	
	protected abstract void onIntentionAttack(Creature target);
	
	protected abstract void onIntentionCast(SkillTemplate request);
	
	protected abstract void onIntentionMoveTo(Position destination);
	
	protected abstract void onIntentionFollow(Creature target);

	protected abstract boolean moveToPawn(VisibleObject pawn, float offset);
	
	public void stopAI()
	{
		target = null;
		followTarget = null;
		stopFollow();
	}

	public abstract void startAI();

	final void clearNextIntention()
	{
		nextIntention = null;
	}

	private final void saveNextIntention(CtrlIntention intention, Object arg0, Object arg1)
	{
		nextIntention = new IntentionCommand(intention, arg0, arg1);
	}

	public final CtrlIntention getNextCtrlIntention()
	{
		return nextIntention == null ? null : nextIntention.crtlIntention;
	}
	
	protected final void executeNextIntention()
	{
		// run interrupted or next intention
		final IntentionCommand _nextIntention = nextIntention;
		if (nextIntention != null)
		{
			clearNextIntention();
			
			setIntention(_nextIntention.crtlIntention, _nextIntention.arg0, _nextIntention.arg1);
		}
		else if (getIntention() == CtrlIntention.CAST)
			setIntention(CtrlIntention.IDLE);
		
		notifyEvent(CtrlEvent.EVT_THINK);
	}

	/**
	 * Return the current Intention.<BR>
	 * <BR>
	 */
	@Override
	public final CtrlIntention getIntention()
	{
		return intention;
	}

	/**
	 * Launch the CharacterAI onEvt method corresponding to the Event.<BR>
	 * <BR>
	 * <FONT COLOR=#FF0000><B> <U>Caution</U> : The current general intention won't be change (ex : If the character
	 * attack and is stunned, he will attack again after the stunned period)</B></FONT><BR>
	 * <BR>
	 * 
	 * @param evt The event whose the AI must be notified
	 */
	@Override
	public final void notifyEvent(CtrlEvent evt)
	{
		notifyEvent(evt, null, null);
	}

	/**
	 * Launch the CharacterAI onEvt method corresponding to the Event.<BR>
	 * <BR>
	 * <FONT COLOR=#FF0000><B> <U>Caution</U> : The current general intention won't be change (ex : If the character
	 * attack and is stunned, he will attack again after the stunned period)</B></FONT><BR>
	 * <BR>
	 * 
	 * @param evt The event whose the AI must be notified
	 * @param arg0 The first parameter of the Event (optional target)
	 */
	@Override
	public final void notifyEvent(CtrlEvent evt, Object arg0)
	{
		notifyEvent(evt, arg0, null);
	}

	/**
	 * Launch the L2CharacterAI onEvt method corresponding to the Event.<BR>
	 * <BR>
	 * <FONT COLOR=#FF0000><B> <U>Caution</U> : The current general intention won't be change (ex : If the character
	 * attack and is stunned, he will attack again after the stunned period)</B></FONT><BR>
	 * <BR>
	 * 
	 * @param evt The event whose the AI must be notified
	 * @param arg0 The first parameter of the Event (optional target)
	 * @param arg1 The second parameter of the Event (optional target)
	 */
	@Override
	public final void notifyEvent(CtrlEvent evt, Object arg0, Object arg1)
	{
		//if (!owner.hasAI())
			//return;
		
		/*
		 if (Config.DEBUG)
		 _log.warning("AbstractAI: notifyEvent -> " + evt + " " + arg0 + " " + arg1);
		 */

		switch (evt)
		{
			case EVT_THINK:
				onEvtThink();
				break;
			case EVT_ATTACKED:
				onEvtAttacked((Creature)arg0);
				break;
			case EVT_AGGRESSION:
				onEvtAggression((Creature)arg0, ((Number)arg1).intValue());
				break;
			case EVT_READY_TO_ACT:
				//if (!_actor.isCastingNow() && !_actor.isCastingSimultaneouslyNow())
				onEvtReadyToAct();
				break;
			case EVT_ARRIVED:
				// happens e.g. from stopmove but we don't process it if we're casting
				//if (!_actor.isCastingNow() && !_actor.isCastingSimultaneouslyNow())
				onEvtArrived();
				break;
			case EVT_ARRIVED_REVALIDATE:
				// this is disregarded if the char is not moving any more
				//if (_actor.isMoving())
				onEvtArrivedRevalidate();
				break;
			case EVT_FORGET_OBJECT:
				onEvtForgetObject((VisibleObject)arg0);
				break;
			case EVT_CANCEL:
				onEvtCancel();
				break;
			case EVT_DEAD:
				onEvtDead();
				break;
			case EVT_FINISH_CASTING:
				onEvtFinishCasting();
				break;
		}
	}
	
	protected abstract void onEvtThink();
	
	protected abstract void onEvtAttacked(Creature attacker);
	
	protected abstract void onEvtAggression(Creature target, int aggro);
	
	protected abstract void onEvtReadyToAct();
	
	protected abstract void onEvtArrived();
	
	protected abstract void onEvtArrivedRevalidate();
	
	protected abstract void onEvtForgetObject(VisibleObject object);
	
	protected abstract void onEvtCancel();
	
	protected abstract void onEvtDead();
	
	protected abstract void onEvtFinishCasting();


	public VisibleObject getFollowTarget()
	{
		return followTarget;
	}
	
	public void setFollowTarget(Creature creature)
	{
		followTarget = creature;
	}

	/**
	 * @return Creature
	 */
	public VisibleObject getTarget()
	{
		return target;
	}

	public void setTarget(Creature target)
	{
		this.target = target;
	}
	
	public final boolean isInsideActingRadius()
	{
		return isInsideActingRadius(followTarget, MathUtil.getDistance(owner, followTarget), followRange);
	}
	
	public final boolean isInsideActingRadius(VisibleObject target, double distance, float range)
	{
		if (target == null)
			return false;
		
		return distance < range;
	}
}
